Area Data IV
NOTE: You can download the source files for this book from here. The source files are in the format of R Notebooks. Notebooks are pretty neat, because the allow you execute code within the notebook, so that you can work interactively with the notes.
In previous practice/session, you learned about different ways to define proximity for area data, about spatial weights matrices, and how spatial weights matrices could be used to calculate spatial moving averages.
If you wish to work interactively with this chapter you will need the following:
Learning objectives
In this practice, you will learn about:
- Decomposing Moran’s \(I\).
- Local Moran’s \(I\) and mapping.
- A concentration approach for local analysis of spatial association.
- A short note on hypothesis testing.
- Detection of hot and cold spots.
Suggested readings
- Bailey TC and Gatrell AC [-@Bailey1995] Interactive Spatial Data Analysis, Chapter 7. Longman: Essex.
- Bivand RS, Pebesma E, and Gomez-Rubio V [-@Bivand2008] Applied Spatial Data Analysis with R, Chapter 9. Springer: New York.
- Brunsdon C and Comber L [-@Brunsdon2015R] An Introduction to R for Spatial Analysis and Mapping, Chapter 7. Sage: Los Angeles.
- O’Sullivan D and Unwin D [-@Osullivan2010] Geographic Information Analysis, 2nd Edition, Chapter 7. John Wiley & Sons: New Jersey.
Preliminaries
As usual, it is good practice to clear the working space to make sure that you do not have extraneous items there when you begin your work. The command in R to clear the workspace is rm (for “remove”), followed by a list of items to be removed. To clear the workspace from all objects, do the following:
rm(list = ls())
Note that ls() lists all objects currently on the worspace.
Load the libraries you will use in this activity:
library(tidyverse)
library(sf)
library(plotly)
library(spdep)
library(crosstalk)
library(geog4ga3)
Load the datasets, first the .RData and then the shape file.
data("df1_simulated")
data("df2_simulated")
These two dataframes are simulated landscapes, one random and one with a strong systematic pattern. Note that the descriptive statistics of both variables are identical.:
summary(df1_simulated)
x y z
Min. : 1.00 Min. : 1.00 Min. :24.40
1st Qu.:27.00 1st Qu.:19.00 1st Qu.:27.89
Median :46.50 Median :33.00 Median :30.33
Mean :45.61 Mean :31.63 Mean :34.38
3rd Qu.:66.00 3rd Qu.:45.00 3rd Qu.:38.25
Max. :87.00 Max. :61.00 Max. :69.59
summary(df2_simulated)
x y z
Min. : 1.00 Min. : 1.00 Min. :24.40
1st Qu.:27.00 1st Qu.:19.00 1st Qu.:27.89
Median :46.50 Median :33.00 Median :30.33
Mean :45.61 Mean :31.63 Mean :34.38
3rd Qu.:66.00 3rd Qu.:45.00 3rd Qu.:38.25
Max. :87.00 Max. :61.00 Max. :69.59
The third dataset is an object of class sf (simple feature) with the census tracts of Hamilton CMA and some selected population variables from the 2011 Census of Canada:
data(Hamilton_CT)
The sf object can be converted into a SpatialPolygonsDataFrame object for use with the spdedp package:
Hamilton_CT.sp <- as(Hamilton_CT, "Spatial")
Decomposing Moran’s I
Recall from the preceding reading and activity that Moran’s I coefficient of spatial autocorrelation was derived based on the idea of aggregating the products of a (mean-centered) variable by its spatial moving average, and then dividing by the variance: \[
I = \frac{\sum_{i=1}^n{z_i\sum_{j=1}^n{w_{ij}^{st}z_j}}}{\sum_{i=1}^{n}{z_i^2}}
\]
Also, you will have seen that when plotting Moran’s scatterplot some observations are highlighted. This is because said observations make a particularly large contribution to \(I\).
It turns out that those contributions are informative in and of themselves, and their analysis can provide more focused information about the spatial pattern.
Consider again the variable POP_DENSIT, population density in the Hamilton CMA. Lets create spatial weights for the census tracts in this system:
Hamilton_CT.w <- nb2listw(poly2nb(pl = Hamilton_CT.sp))
Although Moran’s scatterplot can be obtained easily with the functionmoran.plot, here we will create the scatterplot manually to have better control of its aspect.
First, create a dataframe with the mean-centered variable and scaled variable \(z_i=(x_i-\overline{x})/\sum z_i^2\), and its spatial moving average. Notice that this includes as well a factor variable Type to identify the type of spatial relationship (Low & Low, if both \(z_i\) and its spatial moving average are negative, High & High, if both \(z_i\) and its spatial moving average are positive, and Low & High/High & Low otherwise). This is information is useful for mapping the results:
Hamilton_CT <- mutate(Hamilton_CT,
Z = (POP_DENSITY - mean(POP_DENSITY)) / var(POP_DENSITY),
SMA = lag.listw(Hamilton_CT.w, Z),
Type = factor(ifelse(Z < 0 & SMA < 0, "LL",
ifelse(Z > 0 & SMA > 0, "HH", "HL/LH"))))
Next, create the scatterplot and a choropleth map of the population density. The package plotly is used to create interactive plots. Read more about how to visualize geospatial information with plotly here. The package crosstalk lets us link two plots for brushing.
First, create a SharedData object to link two plots:
#Create a shared data object for brushing
df_msc.sd <- SharedData$new(Hamilton_CT)
The function bscols (for bootstrap columns) is used to array two plotly objects; the first of these is a scatterplot, and the second is a choropleth map of population density.
bscols(
plot_ly(df_msc.sd) %>%
add_markers(x = ~Z, y = ~SMA, color = ~POP_DENSITY, size = ~(Z * SMA), colors = "YlOrRd") %>%
hide_colorbar() %>%
highlight("plotly_selected", persistent = TRUE),
plot_ly(df_msc.sd) %>%
add_sf(split = ~TRACT, color = ~POP_DENSITY, colors = "YlOrRd", showlegend = FALSE) %>%
hide_colorbar() %>%
highlight(dynamic = TRUE, persistent = TRUE)
)
The darker colors are zones with higher population densities. The size of the dots in the scatterplot indicates the contributions of the zone to Moran’s \(I\). The darker colors in the choropleth map are higher population densities.
The plots are linked for brushing: try selecting groups of dots in the scatterplot (double click to clear a selection). Change the color for brushing to select a different group of dots. Can you identify in the map the zones that most contribute to Moran’s \(I\)?
The direct relationship between the dots in the scatterplot and the values of the variable in the map suggest the following decomposition of Moran’s \(I\).
Local Moran’s I and Mapping
A possible decomposition of Moran’s \(I\) into local components is as follows [see @Anselin1995] (Available here): \[
I_i = \frac{z_i}{m_2}\sum_{j=1}^n{w_{ij}^{st}z_j}
\] where \(z_i\) is a mean-centered variable, and: \[
m_2 = \sum_{i=1}^n{z_i^2}
\] is its variance.
It is straightforward to see that: \[
I = \sum_{i=1}^n{I_i}
\]
The above shows how the local Moran’s \(I\) coefficients can be aggregated to the global coefficient.
An advantage of the local decomposition described here is that it allows the analyst to map the statistic to better understand the spatial pattern.
The local version of Moran’s \(I\) is implemented in spdep as localmoran, and can be called with a variable and a set of spatial weights as arguments:
POP_DENSITY.lm <- localmoran(Hamilton_CT$POP_DENSITY, Hamilton_CT.w)
The value (output) of the function is a matrix with local Moran’s \(I\) coefficients, and their corresponding expected values and variances (used for hypothesis testing; more on this next). You can check the summary to verify the contents:
summary(POP_DENSITY.lm)
Ii E.Ii Var.Ii Z.Ii Pr(z > 0)
Min. :-0.62144 Min. :-0.005348 Min. :0.06421 Min. :-1.67885 Min. :0.00000
1st Qu.: 0.00478 1st Qu.:-0.005348 1st Qu.:0.13340 1st Qu.: 0.02345 1st Qu.:0.07352
Median : 0.12523 Median :-0.005348 Median :0.15647 Median : 0.33935 Median :0.36718
Mean : 0.51797 Mean :-0.005348 Mean :0.16681 Mean : 1.32117 Mean :0.31317
3rd Qu.: 0.59384 3rd Qu.:-0.005348 3rd Qu.:0.18876 3rd Qu.: 1.45104 3rd Qu.:0.49065
Max. : 8.30454 Max. :-0.005348 Max. :0.47938 Max. :19.12671 Max. :0.95341
Similar to the global version of Moran’s \(I\), hypothesis testing can be conducted by comparing the empirical statistic to its distribution under the null hypothesis of spatial independence. The function localmoran reports p-values to this end.
For further exploration, join the local statistics to the dataframe:
Hamilton_CT <- left_join(Hamilton_CT,
data.frame(TRACT = Hamilton_CT$TRACT, POP_DENSITY.lm))
Joining, by = "TRACT"
Column `TRACT` joining character vector and factor, coercing into character vector
Hamilton_CT <- rename(Hamilton_CT, p.val = Pr.z...0.)
Now it is possible to map the local statistics:
plot_ly(Hamilton_CT) %>%
add_sf(split = ~(p.val < 0.05), color = ~Type, colors = c("red", "khaki1", "dodgerblue", "dodgerblue4"))
The map above shows whether population density in a zone is high, surrounded by other zones with high population densities (HH), or low, surrounded by zones that also have low population density (LL). Other zones have either low population densities and are surrounded by zones with high population density, or viceversa (HL/LH).
Click on the legend to filter by category of TRUE-FALSE and HH-LL-HL/LH.
This map allows you to identify what we could call the downtown core (from the perspective of population density), and the most suburban-rural census tracts in the Hamilton CMA.
While mapping \(I_i\) or their corresponding p-values is straightforward, I personally find it more useful to map whether the zones are of type HH, LH, or HL/LH. Since such maps are not (to the best of my knowledge) the output of an existing function in an R package, so we will create one here.
localmoran.map <- function(p = p, listw = listw, VAR = VAR, by = by){
require(tidyverse)
require(spdep)
require(plotly)
df_msc <- transmute(p,
key = p[[by]],
Z = (p[[VAR]] - mean(p[[VAR]])) / var(p[[VAR]]),
SMA = lag.listw(listw, Z),
Type = factor(ifelse(Z < 0 & SMA < 0, "LL",
ifelse(Z > 0 & SMA > 0, "HH", "HL/LH"))))
local_I <- localmoran(p[[VAR]], listw)
df_msc <- left_join(df_msc,
data.frame(key = p[[by]], local_I))
df_msc <- rename(df_msc, p.val = Pr.z...0.)
plot_ly(df_msc) %>%
add_sf(split = ~(p.val < 0.05), color = ~Type, colors = c("red", "khaki1", "dodgerblue", "dodgerblue4"))
}
Notice how this function simply replicates the steps that we followed earlier to create the map with the results of the local Moran’s \(I\)s.
To use this function you need as inputs an object of class sf, a listw object with spatial weights, and to define the variable of interest and a unique identifier for the areas (such as their tract identifiers). For example:
localmoran.map(Hamilton_CT, Hamilton_CT.w, "POP_DENSITY", by = "TRACT")
There, the function creates the map as desired.
A Quick Note on Functions
Once that you know the steps needed to complete a task, if the task needs to be repeated many times possibly using different inputs, a function is a way of packing those instructions in a convenient way. That is all.
A Concentration approach for Local Analysis of Spatial Association
The local version of Moran’s I is one of the most widely used tools of a family of measures called Local Statistics of Spatial Association or LISA. It is not the only one, however.
In this section, we will see an alternative way of exploring spatial patterns locally, by means of a concentration approach.
Imagine a landscape with a variable that can be measured in a ratio scale with a true zero point (say, population, income, a contaminant, or property values, variables that do not take negative values and the value of zero indicates complete absence).
Imagine that you stand at a given location on that landscape and survey your surroundings. If your surroundings look very similar to you (i.e., if their elevation is similar, relative to the rest of the landscape), you would take that as evidence of a spatial pattern, at least locally. This is the idea behind spatial autocorrelation analysis.
As an alternative, imagine for instance that the variable of interest is, say, personal income. You might ask “how much of the regional wealth can be found in my neighborhood?” (or, if you prefer, imagine that the variable is a contaminant, and your question would be, how much of it is around here?)
Imagine now that personal income is spatially random. What would you expect the share of the wealth to be in your neighborhood? Would that share change if you moved to any other location?
Lets elaborate this thought experiment. Take the df1 dataframe. The total sum of this variable in the region is 12,034.34. See:
sum(df1_simulated$z)
[1] 12034.34
The following is an interactive plot of variable z in the sample dataframe df1. This variable is spatially random:
plot_ly(df1_simulated, x = ~x, y = ~y, z = ~z,
marker = list(color = ~z, colorscale = c('#FFE1A1', '#683531'), showscale = TRUE)) %>%
add_markers()
Imagine that you stand at coordinates x = 53 and y = 34 (lets call this the focal point), and you survey the landscape within a radius r of 10 (units of distance) of this location. How much wealth is concentrated in the neighborhood of the focal point? Lets see:
xy0 <- c(53, 34)
r <- 10
df1_xy0 <- subset(df1_simulated, sqrt((x - xy0[1])^2 + (y - xy0[2])^2) < r)
sum(df1_xy0$z)
[1] 832.0156
Recall that the total of the variable for the region is 12,034.34.
If you change the radius r to a very large number, the concentration of the variable will simply become the total sum for the region. Essentially, the whole region is the “neighborhood” of the focal point. Try it.
Now, for a fixed radius, change the focal point, and see how much the concentration of the variable changes for its neighborhood. How does the concentration of the variable by focal point?
Lets repeat the thought experiment but now with the landscape shown in the following figure:
plot_ly(df2_simulated, x = ~x, y = ~y, z = ~z,
marker = list(color = ~z, colorscale = c('#FFE1A1', '#683531'), showscale = TRUE)) %>%
add_markers()
Imagine that you stand at the focal point with coordinates x = 53 and y = 34. Can you identify the point in the plot? If you surveyed the neighborhood, what would be the concentration of wealth there? How would that change as you visited different focal points? Lets see (again, recall that the total of the variable for the whole region is 12,034.34):
xy0 <- c(71, 10)
r <- 10
df2_xy0 <- subset(df2_simulated, sqrt((x - xy0[1])^2 + (y - xy0[2])^2) < r)
sum(df2_xy0$z)
[1] 542.8224
Change the focal point. How does the concentration of the variable change?
Lets define the following measure of local concentration (see Getis and Ord, 1992): \[
G_i^*(d)=\frac{\sum_{j=1}^n{w_{ij}x_j}}{\sum_{i=i}^{n}x_{i}}
\]
Notice that the spatial weights are not row-standardized, and in fact must be a binary variable as follows: \[
w_{ij}=\bigg\{\begin{array}{l l}
1\text{ if } d_{ij}\leq d\\
0\text{ otherwise}\\
\end{array}
\]
This is because in this measure of concentration, we do not calculate the spatial moving average for the neighborhood, but the total of the variable in the neighborhood.
A variant of this statistic removes from the sum the value of the variable at i: \[
G_i(d)=\frac{\sum_{j\neq i}^n{w_{ij}x_j}}{\sum_{i=i}^{n}x_{i}}
\]
I do not find this definition to be particularly useful. I suspect it was defined to resemble Moran’s I where an area is not it’s own neighbor - which makes sense in an autocorrelation sense (an area is perfectly autocorrelated with itself). In a concentration approach, not using the value at \(i\) is less appealing.
As with the local version of Moran’s I, it is possible to map the statistic to better understand the spatial pattern.
The \(G_i(d)\) statistic is implemented in spdep as localG, and can be called with a variable and a set of spatial weights as arguments.
Lets calculate this statistic for the two datasets in the example above. This requires that we create binary spatial weights.
Begin by creating neighbors by distance:
xy_coord <- cbind(df1_simulated$x, df1_simulated$y)
dn10 <- dnearneigh(xy_coord, 0, 10)
Two differences with the procedure that you used before to create spatial weights is that we wish to include the observation at \(i\) as well (so include.self(), and the style of the matrix is “B” (for binary):fr
wb10 <- nb2listw(include.self(dn10), style = "B")
The local statistics can be obtained as follows:
df1.lg <- localG(df1_simulated$z, wb10)
The value (output) of the function is a ’vector localG object with normalized local statistics. Normalized means that the mean under the null hypothesis has been substracted and the result has been divided by the variance under the null. Normalized statistics can be compared to the standard normal distribution for hypothesis testing. You can check the summary to verify the contents:
summary(df1.lg)
Min. 1st Qu. Median Mean 3rd Qu. Max.
-1.6345 -0.5085 0.1401 0.0657 0.5911 2.6638
Lets add p-values to this:
df1.lg <- as.numeric(df1.lg)
df1.lg <- data.frame(Gstar = df1.lg, p.val = 2 * pnorm(abs(df1.lg), lower.tail = FALSE))
How many of the p-values are less than the conventional decision cutoff of 0.05?
Now the second example:
df2.lg <- localG(df2_simulated$z, wb10)
summary(df2.lg)
Min. 1st Qu. Median Mean 3rd Qu. Max.
-4.2400 -2.6791 -1.3999 0.1503 2.3938 12.2401
Adding p-values:
df2.lg <- as.numeric(df2.lg)
df2.lg <- data.frame(Gstar = df2.lg, p.val = 2 * pnorm(abs(df2.lg), lower.tail = FALSE))
If we append the results of the analysis to the dataframe, we can plot the results for further exploration. We will classify the results by their type, in this case high and low concentrations:
df2 <- cbind(df2_simulated[,1:3],df2.lg)
df2 <- mutate(df2,
Type = factor(ifelse(Gstar < 0 & p.val <= 0.05, "Low Concentration",
ifelse(Gstar > 0 & p.val <= 0.05, "High Concentration", "Not Signicant"))))
And then the plot:
plot_ly(df2, x = ~x, y = ~y, z = ~z, color = ~Type, colors = c("red", "blue", "gray"),
marker = list()) %>%
add_markers()
What kind of pattern do you observe?
A Short Note on Hypothesis Testing
Local tests as introduced above are affected by an issue called multiple testing. Typically, when attempting to assess the significance of a statistic, a level of significance is adopted (conventionally 0.05, for instance). When working with local statistics, we typically conduct many tests of hypothesis simultaneously (in the example above, one for each observation).
A risk when conducting a large number of tests is that some of them might appear significant purely by chance! The more tests we conduct, the more likely that at least a few of them will be significant by chance. For instance, in the preceding example the variable in df1 was spatially random, and yet a few observations had p-values smaller than 0.05.
What this suggests is that some correction to the level of significance used is needed.
A crude rule to make this adjustment is called Bonferroni correction. This correction is as follows: \[
\alpha_B = \frac{\alpha_{nominal}}{m}
\] where \(\alpha_{nominal}\) is the nominal level of significance, \(\alpha_B\) is the adjusted level of significance, and \(m\) is the number of simultaneous tests. This correction requires that each test be evaluated at a lower level of significance \(\alpha_B\) in order to to achieve a nominal level of significance of 0.05.
If we apply this correction to the analysis above, we see that instead of 0.05, the p-value needed for significance is much lower:
alpha_B <- 0.05/nrow(df1_simulated)
alpha_B
[1] 0.0001428571
You can verify now that no observations in df1 show up as significant:
sum(df1.lg$p.val <= alpha_B)
[1] 0
If we examine the variable in df2:
df2 <- mutate(df2,
Type = factor(ifelse(Gstar < 0 & p.val <= alpha_B, "Low Concentration",
ifelse(Gstar > 0 & p.val <= alpha_B, "High Concentration", "Not Signicant"))))
plot_ly(df2, x = ~x, y = ~y, z = ~z, color = ~Type, colors = c("red", "blue", "gray"),
marker = list()) %>%
add_markers()
You will see that fewer observations are significant, but it is still possible to detect two regions of high concentration, and two of low concentration.
Bonferroni correction is known to be overly strict, and sharper approaches exist to correct for multiple testing. Between the nominal level (no correction) and Bonferroni correction, it is still possible to assess the gravity of the issue of multiple comparisons. Observations that are flagged as significant with the Bonferroni correction, will also be significant under more refined corrections, so it provides a most conservative decision rule.
Detection of Hot and Cold Spots
As the examples above illustrate, local statistics can be very useful in detecting what might be termed “hot” and “cold” spots. A hot spot is a group of observations that are significantly high, whereas a cold spot is a group of observations that are significantly low.
There are many different applications where hot/cold spot detection is important.
For instance, in many studies of urban form, it is important to identify centers and subcenters - by population, by property values, by incidence of trips, and so on. In spatial criminology, detecting hot spots of crime can help with prevention and law enforcement efforts. In environmental studies, remediation efforts can be greatly assisted by identification of hot areas. And so on.
Other Resources
Check a cool app that illustrates the \(G_i^*\) statistic here
LS0tDQp0aXRsZTogIjEyIEFyZWEgRGF0YSBJViINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCiMgQXJlYSBEYXRhIElWDQoNCipOT1RFKjogWW91IGNhbiBkb3dubG9hZCB0aGUgc291cmNlIGZpbGVzIGZvciB0aGlzIGJvb2sgZnJvbSBbaGVyZV0oaHR0cHM6Ly9naXRodWIuY29tL3BhZXpoYS9TcGF0aWFsLVN0YXRpc3RpY3MtQ291cnNlKS4gVGhlIHNvdXJjZSBmaWxlcyBhcmUgaW4gdGhlIGZvcm1hdCBvZiBSIE5vdGVib29rcy4gTm90ZWJvb2tzIGFyZSBwcmV0dHkgbmVhdCwgYmVjYXVzZSB0aGUgYWxsb3cgeW91IGV4ZWN1dGUgY29kZSB3aXRoaW4gdGhlIG5vdGVib29rLCBzbyB0aGF0IHlvdSBjYW4gd29yayBpbnRlcmFjdGl2ZWx5IHdpdGggdGhlIG5vdGVzLiANCg0KSW4gcHJldmlvdXMgcHJhY3RpY2Uvc2Vzc2lvbiwgeW91IGxlYXJuZWQgYWJvdXQgZGlmZmVyZW50IHdheXMgdG8gZGVmaW5lIF9wcm94aW1pdHlfIGZvciBhcmVhIGRhdGEsIGFib3V0IHNwYXRpYWwgd2VpZ2h0cyBtYXRyaWNlcywgYW5kIGhvdyBzcGF0aWFsIHdlaWdodHMgbWF0cmljZXMgY291bGQgYmUgdXNlZCB0byBjYWxjdWxhdGUgc3BhdGlhbCBtb3ZpbmcgYXZlcmFnZXMuIA0KDQpJZiB5b3Ugd2lzaCB0byB3b3JrIGludGVyYWN0aXZlbHkgd2l0aCB0aGlzIGNoYXB0ZXIgeW91IHdpbGwgbmVlZCB0aGUgZm9sbG93aW5nOg0KDQoqIEFuIFIgbWFya2Rvd24gbm90ZWJvb2sgdmVyc2lvbiBvZiB0aGlzIGRvY3VtZW50ICh0aGUgc291cmNlIGZpbGUpLg0KDQoqIEEgcGFja2FnZSBjYWxsZWQgYGdlb2c0Z2EzYC4NCg0KIyMgTGVhcm5pbmcgb2JqZWN0aXZlcw0KDQpJbiB0aGlzIHByYWN0aWNlLCB5b3Ugd2lsbCBsZWFybiBhYm91dDoNCg0KMS4gRGVjb21wb3NpbmcgTW9yYW4ncyAkSSQuIA0KMi4gTG9jYWwgTW9yYW4ncyAkSSQgYW5kIG1hcHBpbmcuDQozLiBBIGNvbmNlbnRyYXRpb24gYXBwcm9hY2ggZm9yIGxvY2FsIGFuYWx5c2lzIG9mIHNwYXRpYWwgYXNzb2NpYXRpb24uDQo0LiBBIHNob3J0IG5vdGUgb24gaHlwb3RoZXNpcyB0ZXN0aW5nLg0KNS4gRGV0ZWN0aW9uIG9mIGhvdCBhbmQgY29sZCBzcG90cy4NCg0KIyMgU3VnZ2VzdGVkIHJlYWRpbmdzDQoNCi0gQmFpbGV5IFRDIGFuZCBHYXRyZWxsIEFDIFstQEJhaWxleTE5OTVdIEludGVyYWN0aXZlIFNwYXRpYWwgRGF0YSBBbmFseXNpcywgQ2hhcHRlciA3LiBMb25nbWFuOiBFc3NleC4NCi0gQml2YW5kIFJTLCBQZWJlc21hIEUsIGFuZCBHb21lei1SdWJpbyBWIFstQEJpdmFuZDIwMDhdIEFwcGxpZWQgU3BhdGlhbCBEYXRhIEFuYWx5c2lzIHdpdGggUiwgQ2hhcHRlciA5LiBTcHJpbmdlcjogTmV3IFlvcmsuDQotIEJydW5zZG9uIEMgYW5kIENvbWJlciBMIFstQEJydW5zZG9uMjAxNVJdIEFuIEludHJvZHVjdGlvbiB0byBSIGZvciBTcGF0aWFsIEFuYWx5c2lzIGFuZCBNYXBwaW5nLCBDaGFwdGVyIDcuIFNhZ2U6IExvcyBBbmdlbGVzLg0KLSBPJ1N1bGxpdmFuIEQgYW5kIFVud2luIEQgWy1AT3N1bGxpdmFuMjAxMF0gR2VvZ3JhcGhpYyBJbmZvcm1hdGlvbiBBbmFseXNpcywgMm5kIEVkaXRpb24sIENoYXB0ZXIgNy4gSm9obiBXaWxleSAmIFNvbnM6IE5ldyBKZXJzZXkuDQoNCiMjIFByZWxpbWluYXJpZXMNCg0KQXMgdXN1YWwsIGl0IGlzIGdvb2QgcHJhY3RpY2UgdG8gY2xlYXIgdGhlIHdvcmtpbmcgc3BhY2UgdG8gbWFrZSBzdXJlIHRoYXQgeW91IGRvIG5vdCBoYXZlIGV4dHJhbmVvdXMgaXRlbXMgdGhlcmUgd2hlbiB5b3UgYmVnaW4geW91ciB3b3JrLiBUaGUgY29tbWFuZCBpbiBSIHRvIGNsZWFyIHRoZSB3b3Jrc3BhY2UgaXMgYHJtYCAoZm9yICJyZW1vdmUiKSwgZm9sbG93ZWQgYnkgYSBsaXN0IG9mIGl0ZW1zIHRvIGJlIHJlbW92ZWQuIFRvIGNsZWFyIHRoZSB3b3Jrc3BhY2UgZnJvbSBfYWxsXyBvYmplY3RzLCBkbyB0aGUgZm9sbG93aW5nOg0KYGBge3J9DQpybShsaXN0ID0gbHMoKSkNCmBgYA0KDQpOb3RlIHRoYXQgYGxzKClgIGxpc3RzIGFsbCBvYmplY3RzIGN1cnJlbnRseSBvbiB0aGUgd29yc3BhY2UuDQoNCkxvYWQgdGhlIGxpYnJhcmllcyB5b3Ugd2lsbCB1c2UgaW4gdGhpcyBhY3Rpdml0eToNCmBgYHtyIG1lc3NhZ2U9RkFMU0V9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkoc2YpDQpsaWJyYXJ5KHBsb3RseSkNCmxpYnJhcnkoc3BkZXApDQpsaWJyYXJ5KGNyb3NzdGFsaykNCmxpYnJhcnkoZ2VvZzRnYTMpDQpgYGANCg0KTG9hZCB0aGUgZGF0YXNldHMsIGZpcnN0IHRoZSAuUkRhdGEgYW5kIHRoZW4gdGhlIHNoYXBlIGZpbGUuDQpgYGB7cn0NCmRhdGEoImRmMV9zaW11bGF0ZWQiKQ0KZGF0YSgiZGYyX3NpbXVsYXRlZCIpDQpgYGANCg0KVGhlc2UgdHdvIGRhdGFmcmFtZXMgYXJlIHNpbXVsYXRlZCBsYW5kc2NhcGVzLCBvbmUgcmFuZG9tIGFuZCBvbmUgd2l0aCBhIHN0cm9uZyBzeXN0ZW1hdGljIHBhdHRlcm4uIE5vdGUgdGhhdCB0aGUgZGVzY3JpcHRpdmUgc3RhdGlzdGljcyBvZiBib3RoIHZhcmlhYmxlcyBhcmUgaWRlbnRpY2FsLjoNCmBgYHtyfQ0Kc3VtbWFyeShkZjFfc2ltdWxhdGVkKQ0Kc3VtbWFyeShkZjJfc2ltdWxhdGVkKQ0KYGBgDQoNClRoZSB0aGlyZCBkYXRhc2V0IGlzIGFuIG9iamVjdCBvZiBjbGFzcyBgc2ZgIChzaW1wbGUgZmVhdHVyZSkgd2l0aCB0aGUgY2Vuc3VzIHRyYWN0cyBvZiBIYW1pbHRvbiBDTUEgYW5kIHNvbWUgc2VsZWN0ZWQgcG9wdWxhdGlvbiB2YXJpYWJsZXMgZnJvbSB0aGUgMjAxMSBDZW5zdXMgb2YgQ2FuYWRhOg0KYGBge3J9DQpkYXRhKEhhbWlsdG9uX0NUKQ0KYGBgDQoNClRoZSBgc2ZgIG9iamVjdCBjYW4gYmUgY29udmVydGVkIGludG8gYSBgU3BhdGlhbFBvbHlnb25zRGF0YUZyYW1lYCBvYmplY3QgZm9yIHVzZSB3aXRoIHRoZSBgc3BkZWRwYCBwYWNrYWdlOg0KYGBge3J9DQpIYW1pbHRvbl9DVC5zcCA8LSBhcyhIYW1pbHRvbl9DVCwgIlNwYXRpYWwiKQ0KYGBgDQoNCiMjIERlY29tcG9zaW5nIE1vcmFuJ3MgSQ0KDQpSZWNhbGwgZnJvbSB0aGUgcHJlY2VkaW5nIHJlYWRpbmcgYW5kIGFjdGl2aXR5IHRoYXQgTW9yYW4ncyBJIGNvZWZmaWNpZW50IG9mIHNwYXRpYWwgYXV0b2NvcnJlbGF0aW9uIHdhcyBkZXJpdmVkIGJhc2VkIG9uIHRoZSBpZGVhIG9mIGFnZ3JlZ2F0aW5nIHRoZSBwcm9kdWN0cyBvZiBhIChtZWFuLWNlbnRlcmVkKSB2YXJpYWJsZSBieSBpdHMgc3BhdGlhbCBtb3ZpbmcgYXZlcmFnZSwgYW5kIHRoZW4gZGl2aWRpbmcgYnkgdGhlIHZhcmlhbmNlOg0KJCQNCkkgPSBcZnJhY3tcc3VtX3tpPTF9Xm57el9pXHN1bV97aj0xfV5ue3dfe2lqfV57c3R9el9qfX19e1xzdW1fe2k9MX1ee259e3pfaV4yfX0NCiQkDQoNCkFsc28sIHlvdSB3aWxsIGhhdmUgc2VlbiB0aGF0IHdoZW4gcGxvdHRpbmcgTW9yYW4ncyBzY2F0dGVycGxvdCBzb21lIG9ic2VydmF0aW9ucyBhcmUgaGlnaGxpZ2h0ZWQuIFRoaXMgaXMgYmVjYXVzZSBzYWlkIG9ic2VydmF0aW9ucyBtYWtlIGEgcGFydGljdWxhcmx5IGxhcmdlIGNvbnRyaWJ1dGlvbiB0byAkSSQuDQoNCkl0IHR1cm5zIG91dCB0aGF0IHRob3NlIGNvbnRyaWJ1dGlvbnMgYXJlIGluZm9ybWF0aXZlIGluIGFuZCBvZiB0aGVtc2VsdmVzLCBhbmQgdGhlaXIgYW5hbHlzaXMgY2FuIHByb3ZpZGUgbW9yZSBmb2N1c2VkIGluZm9ybWF0aW9uIGFib3V0IHRoZSBzcGF0aWFsIHBhdHRlcm4uDQoNCkNvbnNpZGVyIGFnYWluIHRoZSB2YXJpYWJsZSBQT1BfREVOU0lULCBwb3B1bGF0aW9uIGRlbnNpdHkgaW4gdGhlIEhhbWlsdG9uIENNQS4gTGV0cyBjcmVhdGUgc3BhdGlhbCB3ZWlnaHRzIGZvciB0aGUgY2Vuc3VzIHRyYWN0cyBpbiB0aGlzIHN5c3RlbToNCmBgYHtyfQ0KSGFtaWx0b25fQ1QudyA8LSBuYjJsaXN0dyhwb2x5Mm5iKHBsID0gSGFtaWx0b25fQ1Quc3ApKQ0KYGBgDQoNCkFsdGhvdWdoIE1vcmFuJ3Mgc2NhdHRlcnBsb3QgY2FuIGJlIG9idGFpbmVkIGVhc2lseSB3aXRoIHRoZSBmdW5jdGlvbmBtb3Jhbi5wbG90YCwgaGVyZSB3ZSB3aWxsIGNyZWF0ZSB0aGUgc2NhdHRlcnBsb3QgbWFudWFsbHkgdG8gaGF2ZSBiZXR0ZXIgY29udHJvbCBvZiBpdHMgYXNwZWN0Lg0KDQpGaXJzdCwgY3JlYXRlIGEgZGF0YWZyYW1lIHdpdGggdGhlIG1lYW4tY2VudGVyZWQgdmFyaWFibGUgYW5kIHNjYWxlZCB2YXJpYWJsZSAkel9pPSh4X2ktXG92ZXJsaW5le3h9KS9cc3VtIHpfaV4yJCwgYW5kIGl0cyBzcGF0aWFsIG1vdmluZyBhdmVyYWdlLiBOb3RpY2UgdGhhdCB0aGlzIGluY2x1ZGVzIGFzIHdlbGwgYSBmYWN0b3IgdmFyaWFibGUgYFR5cGVgIHRvIGlkZW50aWZ5IHRoZSB0eXBlIG9mIHNwYXRpYWwgcmVsYXRpb25zaGlwIChMb3cgJiBMb3csIGlmIGJvdGggJHpfaSQgYW5kIGl0cyBzcGF0aWFsIG1vdmluZyBhdmVyYWdlIGFyZSBuZWdhdGl2ZSwgSGlnaCAmIEhpZ2gsIGlmIGJvdGggJHpfaSQgYW5kIGl0cyBzcGF0aWFsIG1vdmluZyBhdmVyYWdlIGFyZSBwb3NpdGl2ZSwgYW5kIExvdyAmIEhpZ2gvSGlnaCAmIExvdyBvdGhlcndpc2UpLiBUaGlzIGlzIGluZm9ybWF0aW9uIGlzIHVzZWZ1bCBmb3IgbWFwcGluZyB0aGUgcmVzdWx0czoNCmBgYHtyfQ0KSGFtaWx0b25fQ1QgPC0gbXV0YXRlKEhhbWlsdG9uX0NULA0KICAgICAgICAgICAgICAgICAgICAgIFogPSAoUE9QX0RFTlNJVFkgLSBtZWFuKFBPUF9ERU5TSVRZKSkgLyB2YXIoUE9QX0RFTlNJVFkpLCANCiAgICAgICAgICAgICAgICAgICAgICBTTUEgPSBsYWcubGlzdHcoSGFtaWx0b25fQ1QudywgWiksDQogICAgICAgICAgICAgICAgICAgICAgVHlwZSA9IGZhY3RvcihpZmVsc2UoWiA8IDAgJiBTTUEgPCAwLCAiTEwiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShaID4gMCAmIFNNQSA+IDAsICJISCIsICJITC9MSCIpKSkpDQpgYGANCg0KTmV4dCwgY3JlYXRlIHRoZSBzY2F0dGVycGxvdCBhbmQgYSBjaG9yb3BsZXRoIG1hcCBvZiB0aGUgcG9wdWxhdGlvbiBkZW5zaXR5LiBUaGUgcGFja2FnZSBgcGxvdGx5YCBpcyB1c2VkIHRvIGNyZWF0ZSBpbnRlcmFjdGl2ZSBwbG90cy4gUmVhZCBtb3JlIGFib3V0IGhvdyB0byB2aXN1YWxpemUgZ2Vvc3BhdGlhbCBpbmZvcm1hdGlvbiB3aXRoIHBsb3RseSBbaGVyZV0oI2h0dHBzOi8vbW9kZXJuZGF0YS5wbG90Lmx5L3Zpc3VhbGl6aW5nLWdlby1zcGF0aWFsLWRhdGEtd2l0aC1zZi1hbmQtcGxvdGx5LykuIFRoZSBwYWNrYWdlIGBjcm9zc3RhbGtgIGxldHMgdXMgbGluayB0d28gcGxvdHMgZm9yIGJydXNoaW5nLiANCg0KRmlyc3QsIGNyZWF0ZSBhIGBTaGFyZWREYXRhYCBvYmplY3QgdG8gbGluayB0d28gcGxvdHM6DQpgYGB7cn0NCiNDcmVhdGUgYSBzaGFyZWQgZGF0YSBvYmplY3QgZm9yIGJydXNoaW5nDQpkZl9tc2Muc2QgPC0gU2hhcmVkRGF0YSRuZXcoSGFtaWx0b25fQ1QpDQpgYGANCg0KVGhlIGZ1bmN0aW9uIGBic2NvbHNgIChmb3IgYm9vdHN0cmFwIGNvbHVtbnMpIGlzIHVzZWQgdG8gYXJyYXkgdHdvIHBsb3RseSBvYmplY3RzOyB0aGUgZmlyc3Qgb2YgdGhlc2UgaXMgYSBzY2F0dGVycGxvdCwgYW5kIHRoZSBzZWNvbmQgaXMgYSBjaG9yb3BsZXRoIG1hcCBvZiBwb3B1bGF0aW9uIGRlbnNpdHkuDQpgYGB7ciB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQ0KYnNjb2xzKA0KICBwbG90X2x5KGRmX21zYy5zZCkgJT4lIA0KICAgIGFkZF9tYXJrZXJzKHggPSB+WiwgeSA9IH5TTUEsIGNvbG9yID0gflBPUF9ERU5TSVRZLCBzaXplID0gfihaICogU01BKSwgY29sb3JzID0gIllsT3JSZCIpICU+JQ0KICAgIGhpZGVfY29sb3JiYXIoKSAlPiUNCiAgICBoaWdobGlnaHQoInBsb3RseV9zZWxlY3RlZCIsIHBlcnNpc3RlbnQgPSBUUlVFKSwNCiAgcGxvdF9seShkZl9tc2Muc2QpICU+JQ0KICAgIGFkZF9zZihzcGxpdCA9IH5UUkFDVCwgY29sb3IgPSB+UE9QX0RFTlNJVFksIGNvbG9ycyA9ICJZbE9yUmQiLCBzaG93bGVnZW5kID0gRkFMU0UpICU+JQ0KICAgIGhpZGVfY29sb3JiYXIoKSAlPiUNCiAgICBoaWdobGlnaHQoZHluYW1pYyA9IFRSVUUsIHBlcnNpc3RlbnQgPSBUUlVFKQ0KKQ0KYGBgDQoNClRoZSBkYXJrZXIgY29sb3JzIGFyZSB6b25lcyB3aXRoIGhpZ2hlciBwb3B1bGF0aW9uIGRlbnNpdGllcy4gVGhlIHNpemUgb2YgdGhlIGRvdHMgaW4gdGhlIHNjYXR0ZXJwbG90IGluZGljYXRlcyB0aGUgY29udHJpYnV0aW9ucyBvZiB0aGUgem9uZSB0byBNb3JhbidzICRJJC4gVGhlIGRhcmtlciBjb2xvcnMgaW4gdGhlIGNob3JvcGxldGggbWFwIGFyZSBoaWdoZXIgcG9wdWxhdGlvbiBkZW5zaXRpZXMuIA0KDQpUaGUgcGxvdHMgYXJlIGxpbmtlZCBmb3IgYnJ1c2hpbmc6IHRyeSBzZWxlY3RpbmcgZ3JvdXBzIG9mIGRvdHMgaW4gdGhlIHNjYXR0ZXJwbG90IChkb3VibGUgY2xpY2sgdG8gY2xlYXIgYSBzZWxlY3Rpb24pLiBDaGFuZ2UgdGhlIGNvbG9yIGZvciBicnVzaGluZyB0byBzZWxlY3QgYSBkaWZmZXJlbnQgZ3JvdXAgb2YgZG90cy4gQ2FuIHlvdSBpZGVudGlmeSBpbiB0aGUgbWFwIHRoZSB6b25lcyB0aGF0IG1vc3QgY29udHJpYnV0ZSB0byBNb3JhbidzICRJJD8NCg0KVGhlIGRpcmVjdCByZWxhdGlvbnNoaXAgYmV0d2VlbiB0aGUgZG90cyBpbiB0aGUgc2NhdHRlcnBsb3QgYW5kIHRoZSB2YWx1ZXMgb2YgdGhlIHZhcmlhYmxlIGluIHRoZSBtYXAgc3VnZ2VzdCB0aGUgZm9sbG93aW5nIGRlY29tcG9zaXRpb24gb2YgTW9yYW4ncyAkSSQuDQoNCiMjIExvY2FsIE1vcmFuJ3MgSSBhbmQgTWFwcGluZw0KDQpBIHBvc3NpYmxlIGRlY29tcG9zaXRpb24gb2YgTW9yYW4ncyAkSSQgaW50byBsb2NhbCBjb21wb25lbnRzIGlzIGFzIGZvbGxvd3MgW3NlZSBAQW5zZWxpbjE5OTVdIChBdmFpbGFibGUgW2hlcmVdKGh0dHA6Ly9vbmxpbmVsaWJyYXJ5LndpbGV5LmNvbS9kb2kvMTAuMTExMS9qLjE1MzgtNDYzMi4xOTk1LnRiMDAzMzgueC9hYnN0cmFjdCkpOg0KJCQNCklfaSA9IFxmcmFje3pfaX17bV8yfVxzdW1fe2o9MX1ebnt3X3tpan1ee3N0fXpfan0NCiQkDQp3aGVyZSAkel9pJCBpcyBhIG1lYW4tY2VudGVyZWQgdmFyaWFibGUsIGFuZDoNCiQkDQptXzIgPSBcc3VtX3tpPTF9Xm57el9pXjJ9DQokJA0KaXMgaXRzIHZhcmlhbmNlLg0KDQpJdCBpcyBzdHJhaWdodGZvcndhcmQgdG8gc2VlIHRoYXQ6DQokJA0KSSA9IFxzdW1fe2k9MX1ebntJX2l9DQokJA0KDQpUaGUgYWJvdmUgc2hvd3MgaG93IHRoZSBsb2NhbCBNb3JhbidzICRJJCBjb2VmZmljaWVudHMgY2FuIGJlIGFnZ3JlZ2F0ZWQgdG8gdGhlIGdsb2JhbCBjb2VmZmljaWVudC4NCg0KQW4gYWR2YW50YWdlIG9mIHRoZSBsb2NhbCBkZWNvbXBvc2l0aW9uIGRlc2NyaWJlZCBoZXJlIGlzIHRoYXQgaXQgYWxsb3dzIHRoZSBhbmFseXN0IHRvIG1hcCB0aGUgc3RhdGlzdGljIHRvIGJldHRlciB1bmRlcnN0YW5kIHRoZSBzcGF0aWFsIHBhdHRlcm4uDQoNClRoZSBsb2NhbCB2ZXJzaW9uIG9mIE1vcmFuJ3MgJEkkIGlzIGltcGxlbWVudGVkIGluIGBzcGRlcGAgYXMgYGxvY2FsbW9yYW5gLCBhbmQgY2FuIGJlIGNhbGxlZCB3aXRoIGEgdmFyaWFibGUgYW5kIGEgc2V0IG9mIHNwYXRpYWwgd2VpZ2h0cyBhcyBhcmd1bWVudHM6DQpgYGB7cn0NClBPUF9ERU5TSVRZLmxtIDwtIGxvY2FsbW9yYW4oSGFtaWx0b25fQ1QkUE9QX0RFTlNJVFksIEhhbWlsdG9uX0NULncpDQpgYGANCg0KVGhlIHZhbHVlIChvdXRwdXQpIG9mIHRoZSBmdW5jdGlvbiBpcyBhIG1hdHJpeCB3aXRoIGxvY2FsIE1vcmFuJ3MgJEkkIGNvZWZmaWNpZW50cywgYW5kIHRoZWlyIGNvcnJlc3BvbmRpbmcgZXhwZWN0ZWQgdmFsdWVzIGFuZCB2YXJpYW5jZXMgKHVzZWQgZm9yIGh5cG90aGVzaXMgdGVzdGluZzsgbW9yZSBvbiB0aGlzIG5leHQpLiBZb3UgY2FuIGNoZWNrIHRoZSBzdW1tYXJ5IHRvIHZlcmlmeSB0aGUgY29udGVudHM6DQpgYGB7cn0NCnN1bW1hcnkoUE9QX0RFTlNJVFkubG0pDQpgYGANCg0KU2ltaWxhciB0byB0aGUgZ2xvYmFsIHZlcnNpb24gb2YgTW9yYW4ncyAkSSQsIGh5cG90aGVzaXMgdGVzdGluZyBjYW4gYmUgY29uZHVjdGVkIGJ5IGNvbXBhcmluZyB0aGUgZW1waXJpY2FsIHN0YXRpc3RpYyB0byBpdHMgZGlzdHJpYnV0aW9uIHVuZGVyIHRoZSBudWxsIGh5cG90aGVzaXMgb2Ygc3BhdGlhbCBpbmRlcGVuZGVuY2UuIFRoZSBmdW5jdGlvbiBgbG9jYWxtb3JhbmAgcmVwb3J0cyBwLXZhbHVlcyB0byB0aGlzIGVuZC4NCg0KRm9yIGZ1cnRoZXIgZXhwbG9yYXRpb24sIGpvaW4gdGhlIGxvY2FsIHN0YXRpc3RpY3MgdG8gdGhlIGRhdGFmcmFtZToNCmBgYHtyfQ0KSGFtaWx0b25fQ1QgPC0gbGVmdF9qb2luKEhhbWlsdG9uX0NULCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEuZnJhbWUoVFJBQ1QgPSBIYW1pbHRvbl9DVCRUUkFDVCwgUE9QX0RFTlNJVFkubG0pKQ0KSGFtaWx0b25fQ1QgPC0gcmVuYW1lKEhhbWlsdG9uX0NULCBwLnZhbCA9IFByLnouLi4wLikNCmBgYA0KDQpOb3cgaXQgaXMgcG9zc2libGUgdG8gbWFwIHRoZSBsb2NhbCBzdGF0aXN0aWNzOg0KYGBge3IgbWVzc2FnZSA9IEZBTFNFfQ0KICBwbG90X2x5KEhhbWlsdG9uX0NUKSAlPiUNCiAgICBhZGRfc2Yoc3BsaXQgPSB+KHAudmFsIDwgMC4wNSksIGNvbG9yID0gflR5cGUsIGNvbG9ycyA9IGMoInJlZCIsICJraGFraTEiLCAiZG9kZ2VyYmx1ZSIsICJkb2RnZXJibHVlNCIpKSANCmBgYA0KDQpUaGUgbWFwIGFib3ZlIHNob3dzIHdoZXRoZXIgcG9wdWxhdGlvbiBkZW5zaXR5IGluIGEgem9uZSBpcyBoaWdoLCBzdXJyb3VuZGVkIGJ5IG90aGVyIHpvbmVzIHdpdGggaGlnaCBwb3B1bGF0aW9uIGRlbnNpdGllcyAoSEgpLCBvciBsb3csIHN1cnJvdW5kZWQgYnkgem9uZXMgdGhhdCBhbHNvIGhhdmUgbG93IHBvcHVsYXRpb24gZGVuc2l0eSAoTEwpLiBPdGhlciB6b25lcyBoYXZlIGVpdGhlciBsb3cgcG9wdWxhdGlvbiBkZW5zaXRpZXMgYW5kIGFyZSBzdXJyb3VuZGVkIGJ5IHpvbmVzIHdpdGggaGlnaCBwb3B1bGF0aW9uIGRlbnNpdHksIG9yIHZpY2V2ZXJzYSAoSEwvTEgpLiANCg0KQ2xpY2sgb24gdGhlIGxlZ2VuZCB0byBmaWx0ZXIgYnkgY2F0ZWdvcnkgb2YgVFJVRS1GQUxTRSBhbmQgSEgtTEwtSEwvTEguDQoNClRoaXMgbWFwIGFsbG93cyB5b3UgdG8gaWRlbnRpZnkgd2hhdCB3ZSBjb3VsZCBjYWxsIHRoZSBkb3dudG93biBjb3JlIChmcm9tIHRoZSBwZXJzcGVjdGl2ZSBvZiBwb3B1bGF0aW9uIGRlbnNpdHkpLCBhbmQgdGhlIG1vc3Qgc3VidXJiYW4tcnVyYWwgY2Vuc3VzIHRyYWN0cyBpbiB0aGUgSGFtaWx0b24gQ01BLg0KDQpXaGlsZSBtYXBwaW5nICRJX2kkIG9yIHRoZWlyIGNvcnJlc3BvbmRpbmcgcC12YWx1ZXMgaXMgc3RyYWlnaHRmb3J3YXJkLCBJIHBlcnNvbmFsbHkgZmluZCBpdCBtb3JlIHVzZWZ1bCB0byBtYXAgd2hldGhlciB0aGUgem9uZXMgYXJlIG9mIHR5cGUgSEgsIExILCBvciBITC9MSC4gU2luY2Ugc3VjaCBtYXBzIGFyZSBub3QgKHRvIHRoZSBiZXN0IG9mIG15IGtub3dsZWRnZSkgdGhlIG91dHB1dCBvZiBhbiBleGlzdGluZyBmdW5jdGlvbiBpbiBhbiBSIHBhY2thZ2UsIHNvIHdlIHdpbGwgY3JlYXRlIG9uZSBoZXJlLg0KDQpgYGB7cn0NCmxvY2FsbW9yYW4ubWFwIDwtIGZ1bmN0aW9uKHAgPSBwLCBsaXN0dyA9IGxpc3R3LCBWQVIgPSBWQVIsIGJ5ID0gYnkpew0KICByZXF1aXJlKHRpZHl2ZXJzZSkNCiAgcmVxdWlyZShzcGRlcCkNCiAgcmVxdWlyZShwbG90bHkpDQogIA0KICBkZl9tc2MgPC0gdHJhbnNtdXRlKHAsDQogICAgICAgICAgICAgICAgICAgICAga2V5ID0gcFtbYnldXSwNCiAgICAgICAgICAgICAgICAgICAgICBaID0gKHBbW1ZBUl1dIC0gbWVhbihwW1tWQVJdXSkpIC8gdmFyKHBbW1ZBUl1dKSwNCiAgICAgICAgICAgICAgICAgICAgICBTTUEgPSBsYWcubGlzdHcobGlzdHcsIFopLA0KICAgICAgICAgICAgICAgICAgICAgIFR5cGUgPSBmYWN0b3IoaWZlbHNlKFogPCAwICYgU01BIDwgMCwgIkxMIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UoWiA+IDAgJiBTTUEgPiAwLCAiSEgiLCAiSEwvTEgiKSkpKQ0KICANCiAgbG9jYWxfSSA8LSBsb2NhbG1vcmFuKHBbW1ZBUl1dLCBsaXN0dykNCiAgDQogIGRmX21zYyA8LSBsZWZ0X2pvaW4oZGZfbXNjLCANCiAgICAgICAgICAgICAgICAgIGRhdGEuZnJhbWUoa2V5ID0gcFtbYnldXSwgbG9jYWxfSSkpDQogIGRmX21zYyA8LSByZW5hbWUoZGZfbXNjLCBwLnZhbCA9IFByLnouLi4wLikNCiAgDQogIHBsb3RfbHkoZGZfbXNjKSAlPiUNCiAgICBhZGRfc2Yoc3BsaXQgPSB+KHAudmFsIDwgMC4wNSksIGNvbG9yID0gflR5cGUsIGNvbG9ycyA9IGMoInJlZCIsICJraGFraTEiLCAiZG9kZ2VyYmx1ZSIsICJkb2RnZXJibHVlNCIpKSANCn0NCmBgYA0KDQpOb3RpY2UgaG93IHRoaXMgZnVuY3Rpb24gc2ltcGx5IHJlcGxpY2F0ZXMgdGhlIHN0ZXBzIHRoYXQgd2UgZm9sbG93ZWQgZWFybGllciB0byBjcmVhdGUgdGhlIG1hcCB3aXRoIHRoZSByZXN1bHRzIG9mIHRoZSBsb2NhbCBNb3JhbidzICRJJHMuDQoNClRvIHVzZSB0aGlzIGZ1bmN0aW9uIHlvdSBuZWVkIGFzIGlucHV0cyBhbiBvYmplY3Qgb2YgY2xhc3MgYHNmYCwgYSBgbGlzdHdgIG9iamVjdCB3aXRoIHNwYXRpYWwgd2VpZ2h0cywgYW5kIHRvIGRlZmluZSB0aGUgdmFyaWFibGUgb2YgaW50ZXJlc3QgYW5kIGEgdW5pcXVlIGlkZW50aWZpZXIgZm9yIHRoZSBhcmVhcyAoc3VjaCBhcyB0aGVpciB0cmFjdCBpZGVudGlmaWVycykuIEZvciBleGFtcGxlOg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmxvY2FsbW9yYW4ubWFwKEhhbWlsdG9uX0NULCBIYW1pbHRvbl9DVC53LCAiUE9QX0RFTlNJVFkiLCBieSA9ICJUUkFDVCIpDQpgYGANCg0KVGhlcmUsIHRoZSBmdW5jdGlvbiBjcmVhdGVzIHRoZSBtYXAgYXMgZGVzaXJlZC4NCg0KIyMgQSBRdWljayBOb3RlIG9uIEZ1bmN0aW9ucw0KDQpPbmNlIHRoYXQgeW91IGtub3cgdGhlIHN0ZXBzIG5lZWRlZCB0byBjb21wbGV0ZSBhIHRhc2ssIGlmIHRoZSB0YXNrIG5lZWRzIHRvIGJlIHJlcGVhdGVkIG1hbnkgdGltZXMgcG9zc2libHkgdXNpbmcgZGlmZmVyZW50IGlucHV0cywgYSBmdW5jdGlvbiBpcyBhIHdheSBvZiBwYWNraW5nIHRob3NlIGluc3RydWN0aW9ucyBpbiBhIGNvbnZlbmllbnQgd2F5LiBUaGF0IGlzIGFsbC4NCg0KIyMgQSBDb25jZW50cmF0aW9uIGFwcHJvYWNoIGZvciBMb2NhbCBBbmFseXNpcyBvZiBTcGF0aWFsIEFzc29jaWF0aW9uDQoNClRoZSBsb2NhbCB2ZXJzaW9uIG9mIE1vcmFuJ3MgSSBpcyBvbmUgb2YgdGhlIG1vc3Qgd2lkZWx5IHVzZWQgdG9vbHMgb2YgYSBmYW1pbHkgb2YgbWVhc3VyZXMgY2FsbGVkIF9Mb2NhbCBTdGF0aXN0aWNzIG9mIFNwYXRpYWwgQXNzb2NpYXRpb25fIG9yIExJU0EuIEl0IGlzIG5vdCB0aGUgb25seSBvbmUsIGhvd2V2ZXIuDQoNCkluIHRoaXMgc2VjdGlvbiwgd2Ugd2lsbCBzZWUgYW4gYWx0ZXJuYXRpdmUgd2F5IG9mIGV4cGxvcmluZyBzcGF0aWFsIHBhdHRlcm5zIGxvY2FsbHksIGJ5IG1lYW5zIG9mIGEgY29uY2VudHJhdGlvbiBhcHByb2FjaC4NCg0KSW1hZ2luZSBhIGxhbmRzY2FwZSB3aXRoIGEgdmFyaWFibGUgdGhhdCBjYW4gYmUgbWVhc3VyZWQgaW4gYSByYXRpbyBzY2FsZSB3aXRoIGEgdHJ1ZSB6ZXJvIHBvaW50IChzYXksIHBvcHVsYXRpb24sIGluY29tZSwgYSBjb250YW1pbmFudCwgb3IgcHJvcGVydHkgdmFsdWVzLCB2YXJpYWJsZXMgdGhhdCBkbyBub3QgdGFrZSBuZWdhdGl2ZSB2YWx1ZXMgYW5kIHRoZSB2YWx1ZSBvZiB6ZXJvIGluZGljYXRlcyBjb21wbGV0ZSBhYnNlbmNlKS4NCg0KSW1hZ2luZSB0aGF0IHlvdSBzdGFuZCBhdCBhIGdpdmVuIGxvY2F0aW9uIG9uIHRoYXQgbGFuZHNjYXBlIGFuZCBzdXJ2ZXkgeW91ciBzdXJyb3VuZGluZ3MuIElmIHlvdXIgc3Vycm91bmRpbmdzIGxvb2sgdmVyeSBzaW1pbGFyIHRvIHlvdSAoaS5lLiwgaWYgdGhlaXIgZWxldmF0aW9uIGlzIHNpbWlsYXIsIHJlbGF0aXZlIHRvIHRoZSByZXN0IG9mIHRoZSBsYW5kc2NhcGUpLCB5b3Ugd291bGQgdGFrZSB0aGF0IGFzIGV2aWRlbmNlIG9mIGEgc3BhdGlhbCBwYXR0ZXJuLCBhdCBsZWFzdCBsb2NhbGx5LiBUaGlzIGlzIHRoZSBpZGVhIGJlaGluZCBzcGF0aWFsIGF1dG9jb3JyZWxhdGlvbiBhbmFseXNpcy4NCg0KQXMgYW4gYWx0ZXJuYXRpdmUsIGltYWdpbmUgZm9yIGluc3RhbmNlIHRoYXQgdGhlIHZhcmlhYmxlIG9mIGludGVyZXN0IGlzLCBzYXksIHBlcnNvbmFsIGluY29tZS4gWW91IG1pZ2h0IGFzayAiaG93IG11Y2ggb2YgdGhlIHJlZ2lvbmFsIHdlYWx0aCBjYW4gYmUgZm91bmQgaW4gbXkgbmVpZ2hib3Job29kPyIgKG9yLCBpZiB5b3UgcHJlZmVyLCBpbWFnaW5lIHRoYXQgdGhlIHZhcmlhYmxlIGlzIGEgY29udGFtaW5hbnQsIGFuZCB5b3VyIHF1ZXN0aW9uIHdvdWxkIGJlLCBob3cgbXVjaCBvZiBpdCBpcyBhcm91bmQgaGVyZT8pDQoNCkltYWdpbmUgbm93IHRoYXQgcGVyc29uYWwgaW5jb21lIGlzIHNwYXRpYWxseSByYW5kb20uIFdoYXQgd291bGQgeW91IGV4cGVjdCB0aGUgc2hhcmUgb2YgdGhlIHdlYWx0aCB0byBiZSBpbiB5b3VyIG5laWdoYm9yaG9vZD8gV291bGQgdGhhdCBzaGFyZSBjaGFuZ2UgaWYgeW91IG1vdmVkIHRvIGFueSBvdGhlciBsb2NhdGlvbj8NCg0KTGV0cyBlbGFib3JhdGUgdGhpcyB0aG91Z2h0IGV4cGVyaW1lbnQuIFRha2UgdGhlIGBkZjFgIGRhdGFmcmFtZS4gVGhlIHRvdGFsIHN1bSBvZiB0aGlzIHZhcmlhYmxlIGluIHRoZSByZWdpb24gaXMgMTIsMDM0LjM0LiBTZWU6DQpgYGB7cn0NCnN1bShkZjFfc2ltdWxhdGVkJHopDQpgYGANCg0KVGhlIGZvbGxvd2luZyBpcyBhbiBpbnRlcmFjdGl2ZSBwbG90IG9mIHZhcmlhYmxlIGB6YCBpbiB0aGUgc2FtcGxlIGRhdGFmcmFtZSBgZGYxYC4gVGhpcyB2YXJpYWJsZSBpcyBzcGF0aWFsbHkgcmFuZG9tOg0KYGBge3J9DQpwbG90X2x5KGRmMV9zaW11bGF0ZWQsIHggPSB+eCwgeSA9IH55LCB6ID0gfnosDQogICAgICAgIG1hcmtlciA9IGxpc3QoY29sb3IgPSB+eiwgY29sb3JzY2FsZSA9IGMoJyNGRkUxQTEnLCAnIzY4MzUzMScpLCBzaG93c2NhbGUgPSBUUlVFKSkgJT4lDQogIGFkZF9tYXJrZXJzKCkNCmBgYA0KDQpJbWFnaW5lIHRoYXQgeW91IHN0YW5kIGF0IGNvb3JkaW5hdGVzIHggPSA1MyBhbmQgeSA9IDM0IChsZXRzIGNhbGwgdGhpcyB0aGUgZm9jYWwgcG9pbnQpLCBhbmQgeW91IHN1cnZleSB0aGUgbGFuZHNjYXBlIHdpdGhpbiBhIHJhZGl1cyByIG9mIDEwICh1bml0cyBvZiBkaXN0YW5jZSkgb2YgdGhpcyBsb2NhdGlvbi4gSG93IG11Y2ggd2VhbHRoIGlzIGNvbmNlbnRyYXRlZCBpbiB0aGUgbmVpZ2hib3Job29kIG9mIHRoZSBmb2NhbCBwb2ludD8gTGV0cyBzZWU6DQpgYGB7cn0NCnh5MCA8LSBjKDUzLCAzNCkNCnIgPC0gMTANCmRmMV94eTAgPC0gc3Vic2V0KGRmMV9zaW11bGF0ZWQsIHNxcnQoKHggLSB4eTBbMV0pXjIgKyAoeSAtIHh5MFsyXSleMikgPCByKQ0Kc3VtKGRmMV94eTAkeikNCmBgYA0KDQpSZWNhbGwgdGhhdCB0aGUgdG90YWwgb2YgdGhlIHZhcmlhYmxlIGZvciB0aGUgcmVnaW9uIGlzIDEyLDAzNC4zNC4NCg0KSWYgeW91IGNoYW5nZSB0aGUgcmFkaXVzIHIgdG8gYSB2ZXJ5IGxhcmdlIG51bWJlciwgdGhlIGNvbmNlbnRyYXRpb24gb2YgdGhlIHZhcmlhYmxlIHdpbGwgc2ltcGx5IGJlY29tZSB0aGUgdG90YWwgc3VtIGZvciB0aGUgcmVnaW9uLiBFc3NlbnRpYWxseSwgdGhlIHdob2xlIHJlZ2lvbiBpcyB0aGUgIm5laWdoYm9yaG9vZCIgb2YgdGhlIGZvY2FsIHBvaW50LiBUcnkgaXQuDQoNCk5vdywgZm9yIGEgZml4ZWQgcmFkaXVzLCBjaGFuZ2UgdGhlIGZvY2FsIHBvaW50LCBhbmQgc2VlIGhvdyBtdWNoIHRoZSBjb25jZW50cmF0aW9uIG9mIHRoZSB2YXJpYWJsZSBjaGFuZ2VzIGZvciBpdHMgbmVpZ2hib3Job29kLiBIb3cgZG9lcyB0aGUgY29uY2VudHJhdGlvbiBvZiB0aGUgdmFyaWFibGUgYnkgZm9jYWwgcG9pbnQ/DQoNCkxldHMgcmVwZWF0IHRoZSB0aG91Z2h0IGV4cGVyaW1lbnQgYnV0IG5vdyB3aXRoIHRoZSBsYW5kc2NhcGUgc2hvd24gaW4gdGhlIGZvbGxvd2luZyBmaWd1cmU6DQpgYGB7cn0NCnBsb3RfbHkoZGYyX3NpbXVsYXRlZCwgeCA9IH54LCB5ID0gfnksIHogPSB+eiwNCiAgICAgICAgbWFya2VyID0gbGlzdChjb2xvciA9IH56LCBjb2xvcnNjYWxlID0gYygnI0ZGRTFBMScsICcjNjgzNTMxJyksIHNob3dzY2FsZSA9IFRSVUUpKSAlPiUNCiAgYWRkX21hcmtlcnMoKQ0KYGBgDQoNCkltYWdpbmUgdGhhdCB5b3Ugc3RhbmQgYXQgdGhlIGZvY2FsIHBvaW50IHdpdGggY29vcmRpbmF0ZXMgeCA9IDUzIGFuZCB5ID0gMzQuIENhbiB5b3UgaWRlbnRpZnkgdGhlIHBvaW50IGluIHRoZSBwbG90PyBJZiB5b3Ugc3VydmV5ZWQgdGhlIG5laWdoYm9yaG9vZCwgd2hhdCB3b3VsZCBiZSB0aGUgY29uY2VudHJhdGlvbiBvZiB3ZWFsdGggdGhlcmU/IEhvdyB3b3VsZCB0aGF0IGNoYW5nZSBhcyB5b3UgdmlzaXRlZCBkaWZmZXJlbnQgZm9jYWwgcG9pbnRzPyBMZXRzIHNlZSAoYWdhaW4sIHJlY2FsbCB0aGF0IHRoZSB0b3RhbCBvZiB0aGUgdmFyaWFibGUgZm9yIHRoZSB3aG9sZSByZWdpb24gaXMgMTIsMDM0LjM0KToNCmBgYHtyfQ0KeHkwIDwtIGMoNzEsIDEwKQ0KciA8LSAxMA0KZGYyX3h5MCA8LSBzdWJzZXQoZGYyX3NpbXVsYXRlZCwgc3FydCgoeCAtIHh5MFsxXSleMiArICh5IC0geHkwWzJdKV4yKSA8IHIpDQpzdW0oZGYyX3h5MCR6KQ0KYGBgDQoNCkNoYW5nZSB0aGUgZm9jYWwgcG9pbnQuIEhvdyBkb2VzIHRoZSBjb25jZW50cmF0aW9uIG9mIHRoZSB2YXJpYWJsZSBjaGFuZ2U/DQoNCg0KTGV0cyBkZWZpbmUgdGhlIGZvbGxvd2luZyBtZWFzdXJlIG9mIGxvY2FsIGNvbmNlbnRyYXRpb24gKHNlZSBbR2V0aXMgYW5kIE9yZCwgMTk5Ml0oaHR0cDovL29ubGluZWxpYnJhcnkud2lsZXkuY29tL2RvaS8xMC4xMTExL2ouMTUzOC00NjMyLjE5OTIudGIwMDI2MS54L3BkZikpOg0KJCQNCkdfaV4qKGQpPVxmcmFje1xzdW1fe2o9MX1ebnt3X3tpan14X2p9fXtcc3VtX3tpPWl9XntufXhfe2l9fQ0KJCQNCg0KTm90aWNlIHRoYXQgdGhlIHNwYXRpYWwgd2VpZ2h0cyBhcmUgKipub3QqKiByb3ctc3RhbmRhcmRpemVkLCBhbmQgaW4gZmFjdCBtdXN0IGJlIGEgYmluYXJ5IHZhcmlhYmxlIGFzIGZvbGxvd3M6DQokJA0Kd197aWp9PVxiaWdnXHtcYmVnaW57YXJyYXl9e2wgbH0NCjFcdGV4dHsgaWYgfSBkX3tpan1cbGVxIGRcXA0KMFx0ZXh0eyBvdGhlcndpc2V9XFwNClxlbmR7YXJyYXl9DQokJA0KDQpUaGlzIGlzIGJlY2F1c2UgaW4gdGhpcyBtZWFzdXJlIG9mIGNvbmNlbnRyYXRpb24sIHdlIGRvIG5vdCBjYWxjdWxhdGUgdGhlIHNwYXRpYWwgbW92aW5nIGF2ZXJhZ2UgZm9yIHRoZSBuZWlnaGJvcmhvb2QsIGJ1dCB0aGUgdG90YWwgb2YgdGhlIHZhcmlhYmxlIGluIHRoZSBuZWlnaGJvcmhvb2QuDQoNCkEgdmFyaWFudCBvZiB0aGlzIHN0YXRpc3RpYyByZW1vdmVzIGZyb20gdGhlIHN1bSB0aGUgdmFsdWUgb2YgdGhlIHZhcmlhYmxlIGF0IGk6DQokJA0KR19pKGQpPVxmcmFje1xzdW1fe2pcbmVxIGl9Xm57d197aWp9eF9qfX17XHN1bV97aT1pfV57bn14X3tpfX0NCiQkDQoNCkkgZG8gbm90IGZpbmQgdGhpcyBkZWZpbml0aW9uIHRvIGJlIHBhcnRpY3VsYXJseSB1c2VmdWwuIEkgc3VzcGVjdCBpdCB3YXMgZGVmaW5lZCB0byByZXNlbWJsZSBNb3JhbidzIEkgd2hlcmUgYW4gYXJlYSBpcyBub3QgaXQncyBvd24gbmVpZ2hib3IgLSB3aGljaCBtYWtlcyBzZW5zZSBpbiBhbiBhdXRvY29ycmVsYXRpb24gc2Vuc2UgKGFuIGFyZWEgaXMgcGVyZmVjdGx5IGF1dG9jb3JyZWxhdGVkIHdpdGggaXRzZWxmKS4gSW4gYSBjb25jZW50cmF0aW9uIGFwcHJvYWNoLCBub3QgdXNpbmcgdGhlIHZhbHVlIGF0ICRpJCBpcyBsZXNzIGFwcGVhbGluZy4NCg0KQXMgd2l0aCB0aGUgbG9jYWwgdmVyc2lvbiBvZiBNb3JhbidzIEksIGl0IGlzIHBvc3NpYmxlIHRvIG1hcCB0aGUgc3RhdGlzdGljIHRvIGJldHRlciB1bmRlcnN0YW5kIHRoZSBzcGF0aWFsIHBhdHRlcm4uDQoNClRoZSAkR19pKGQpJCBzdGF0aXN0aWMgaXMgaW1wbGVtZW50ZWQgaW4gYHNwZGVwYCBhcyBgbG9jYWxHYCwgYW5kIGNhbiBiZSBjYWxsZWQgd2l0aCBhIHZhcmlhYmxlIGFuZCBhIHNldCBvZiBzcGF0aWFsIHdlaWdodHMgYXMgYXJndW1lbnRzLg0KDQpMZXRzIGNhbGN1bGF0ZSB0aGlzIHN0YXRpc3RpYyBmb3IgdGhlIHR3byBkYXRhc2V0cyBpbiB0aGUgZXhhbXBsZSBhYm92ZS4gVGhpcyByZXF1aXJlcyB0aGF0IHdlIGNyZWF0ZSBiaW5hcnkgc3BhdGlhbCB3ZWlnaHRzLiANCg0KQmVnaW4gYnkgY3JlYXRpbmcgbmVpZ2hib3JzIGJ5IGRpc3RhbmNlOg0KYGBge3J9DQp4eV9jb29yZCA8LSBjYmluZChkZjFfc2ltdWxhdGVkJHgsIGRmMV9zaW11bGF0ZWQkeSkNCmRuMTAgPC0gZG5lYXJuZWlnaCh4eV9jb29yZCwgMCwgMTApDQpgYGANCg0KVHdvIGRpZmZlcmVuY2VzIHdpdGggdGhlIHByb2NlZHVyZSB0aGF0IHlvdSB1c2VkIGJlZm9yZSB0byBjcmVhdGUgc3BhdGlhbCB3ZWlnaHRzIGlzIHRoYXQgd2Ugd2lzaCB0byBpbmNsdWRlIHRoZSBvYnNlcnZhdGlvbiBhdCAkaSQgYXMgd2VsbCAoc28gYGluY2x1ZGUuc2VsZigpYCwgYW5kIHRoZSBzdHlsZSBvZiB0aGUgbWF0cml4IGlzICJCIiAoZm9yIGJpbmFyeSk6ZnINCmBgYHtyfQ0Kd2IxMCA8LSBuYjJsaXN0dyhpbmNsdWRlLnNlbGYoZG4xMCksIHN0eWxlID0gIkIiKQ0KYGBgDQoNClRoZSBsb2NhbCBzdGF0aXN0aWNzIGNhbiBiZSBvYnRhaW5lZCBhcyBmb2xsb3dzOg0KYGBge3J9DQpkZjEubGcgPC0gbG9jYWxHKGRmMV9zaW11bGF0ZWQkeiwgd2IxMCkNCmBgYA0KDQpUaGUgdmFsdWUgKG91dHB1dCkgb2YgdGhlIGZ1bmN0aW9uIGlzIGEgJ3ZlY3RvciBgbG9jYWxHYCBvYmplY3Qgd2l0aCBub3JtYWxpemVkIGxvY2FsIHN0YXRpc3RpY3MuIE5vcm1hbGl6ZWQgbWVhbnMgdGhhdCB0aGUgbWVhbiB1bmRlciB0aGUgbnVsbCBoeXBvdGhlc2lzIGhhcyBiZWVuIHN1YnN0cmFjdGVkIGFuZCB0aGUgcmVzdWx0IGhhcyBiZWVuIGRpdmlkZWQgYnkgdGhlIHZhcmlhbmNlIHVuZGVyIHRoZSBudWxsLiBOb3JtYWxpemVkIHN0YXRpc3RpY3MgY2FuIGJlIGNvbXBhcmVkIHRvIHRoZSBzdGFuZGFyZCBub3JtYWwgZGlzdHJpYnV0aW9uIGZvciBoeXBvdGhlc2lzIHRlc3RpbmcuIFlvdSBjYW4gY2hlY2sgdGhlIHN1bW1hcnkgdG8gdmVyaWZ5IHRoZSBjb250ZW50czoNCmBgYHtyfQ0Kc3VtbWFyeShkZjEubGcpDQpgYGANCg0KTGV0cyBhZGQgcC12YWx1ZXMgdG8gdGhpczoNCmBgYHtyfQ0KZGYxLmxnIDwtIGFzLm51bWVyaWMoZGYxLmxnKQ0KZGYxLmxnIDwtIGRhdGEuZnJhbWUoR3N0YXIgPSBkZjEubGcsIHAudmFsID0gMiAqIHBub3JtKGFicyhkZjEubGcpLCBsb3dlci50YWlsID0gRkFMU0UpKQ0KYGBgDQoNCkhvdyBtYW55IG9mIHRoZSBwLXZhbHVlcyBhcmUgbGVzcyB0aGFuIHRoZSBjb252ZW50aW9uYWwgZGVjaXNpb24gY3V0b2ZmIG9mIDAuMDU/DQoNCk5vdyB0aGUgc2Vjb25kIGV4YW1wbGU6DQpgYGB7cn0NCmRmMi5sZyA8LSBsb2NhbEcoZGYyX3NpbXVsYXRlZCR6LCB3YjEwKQ0Kc3VtbWFyeShkZjIubGcpDQpgYGANCg0KQWRkaW5nIHAtdmFsdWVzOg0KYGBge3J9DQpkZjIubGcgPC0gYXMubnVtZXJpYyhkZjIubGcpDQpkZjIubGcgPC0gZGF0YS5mcmFtZShHc3RhciA9IGRmMi5sZywgcC52YWwgPSAyICogcG5vcm0oYWJzKGRmMi5sZyksIGxvd2VyLnRhaWwgPSBGQUxTRSkpDQpgYGANCg0KSWYgd2UgYXBwZW5kIHRoZSByZXN1bHRzIG9mIHRoZSBhbmFseXNpcyB0byB0aGUgZGF0YWZyYW1lLCB3ZSBjYW4gcGxvdCB0aGUgcmVzdWx0cyBmb3IgZnVydGhlciBleHBsb3JhdGlvbi4gV2Ugd2lsbCBjbGFzc2lmeSB0aGUgcmVzdWx0cyBieSB0aGVpciB0eXBlLCBpbiB0aGlzIGNhc2UgaGlnaCBhbmQgbG93IGNvbmNlbnRyYXRpb25zOg0KYGBge3J9DQpkZjIgPC0gY2JpbmQoZGYyX3NpbXVsYXRlZFssMTozXSxkZjIubGcpDQpkZjIgPC0gbXV0YXRlKGRmMiwgDQogICAgICAgICAgICAgIFR5cGUgPSBmYWN0b3IoaWZlbHNlKEdzdGFyIDwgMCAmIHAudmFsIDw9IDAuMDUsICJMb3cgQ29uY2VudHJhdGlvbiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShHc3RhciA+IDAgJiBwLnZhbCA8PSAwLjA1LCAiSGlnaCBDb25jZW50cmF0aW9uIiwgIk5vdCBTaWduaWNhbnQiKSkpKQ0KYGBgDQoNCkFuZCB0aGVuIHRoZSBwbG90Og0KYGBge3J9DQpwbG90X2x5KGRmMiwgeCA9IH54LCB5ID0gfnksIHogPSB+eiwgY29sb3IgPSB+VHlwZSwgY29sb3JzID0gYygicmVkIiwgImJsdWUiLCAiZ3JheSIpLA0KICAgICAgICBtYXJrZXIgPSBsaXN0KCkpICU+JQ0KICBhZGRfbWFya2VycygpDQpgYGANCg0KV2hhdCBraW5kIG9mIHBhdHRlcm4gZG8geW91IG9ic2VydmU/DQoNCiMjIEEgU2hvcnQgTm90ZSBvbiBIeXBvdGhlc2lzIFRlc3RpbmcNCg0KTG9jYWwgdGVzdHMgYXMgaW50cm9kdWNlZCBhYm92ZSBhcmUgYWZmZWN0ZWQgYnkgYW4gaXNzdWUgY2FsbGVkIF9tdWx0aXBsZSB0ZXN0aW5nXy4gVHlwaWNhbGx5LCB3aGVuIGF0dGVtcHRpbmcgdG8gYXNzZXNzIHRoZSBzaWduaWZpY2FuY2Ugb2YgYSBzdGF0aXN0aWMsIGEgbGV2ZWwgb2Ygc2lnbmlmaWNhbmNlIGlzIGFkb3B0ZWQgKGNvbnZlbnRpb25hbGx5IDAuMDUsIGZvciBpbnN0YW5jZSkuIFdoZW4gd29ya2luZyB3aXRoIGxvY2FsIHN0YXRpc3RpY3MsIHdlIHR5cGljYWxseSBjb25kdWN0IG1hbnkgdGVzdHMgb2YgaHlwb3RoZXNpcyBzaW11bHRhbmVvdXNseSAoaW4gdGhlIGV4YW1wbGUgYWJvdmUsIG9uZSBmb3IgZWFjaCBvYnNlcnZhdGlvbikuDQoNCkEgcmlzayB3aGVuIGNvbmR1Y3RpbmcgYSBsYXJnZSBudW1iZXIgb2YgdGVzdHMgaXMgdGhhdCBzb21lIG9mIHRoZW0gbWlnaHQgYXBwZWFyIHNpZ25pZmljYW50IF9wdXJlbHkgYnkgY2hhbmNlIV8gVGhlIG1vcmUgdGVzdHMgd2UgY29uZHVjdCwgdGhlIG1vcmUgbGlrZWx5IHRoYXQgYXQgbGVhc3QgYSBmZXcgb2YgdGhlbSB3aWxsIGJlIHNpZ25pZmljYW50IGJ5IGNoYW5jZS4gRm9yIGluc3RhbmNlLCBpbiB0aGUgcHJlY2VkaW5nIGV4YW1wbGUgdGhlIHZhcmlhYmxlIGluIGBkZjFgIHdhcyBzcGF0aWFsbHkgcmFuZG9tLCBhbmQgeWV0IGEgZmV3IG9ic2VydmF0aW9ucyBoYWQgcC12YWx1ZXMgc21hbGxlciB0aGFuIDAuMDUuDQoNCldoYXQgdGhpcyBzdWdnZXN0cyBpcyB0aGF0IHNvbWUgY29ycmVjdGlvbiB0byB0aGUgbGV2ZWwgb2Ygc2lnbmlmaWNhbmNlIHVzZWQgaXMgbmVlZGVkLg0KDQpBIGNydWRlIHJ1bGUgdG8gbWFrZSB0aGlzIGFkanVzdG1lbnQgaXMgY2FsbGVkIF9Cb25mZXJyb25pIGNvcnJlY3Rpb25fLiBUaGlzIGNvcnJlY3Rpb24gaXMgYXMgZm9sbG93czoNCiQkDQpcYWxwaGFfQiA9IFxmcmFje1xhbHBoYV97bm9taW5hbH19e219DQokJA0Kd2hlcmUgJFxhbHBoYV97bm9taW5hbH0kIGlzIHRoZSBub21pbmFsIGxldmVsIG9mIHNpZ25pZmljYW5jZSwgJFxhbHBoYV9CJCBpcyB0aGUgYWRqdXN0ZWQgbGV2ZWwgb2Ygc2lnbmlmaWNhbmNlLCBhbmQgJG0kIGlzIHRoZSBudW1iZXIgb2Ygc2ltdWx0YW5lb3VzIHRlc3RzLiBUaGlzIGNvcnJlY3Rpb24gcmVxdWlyZXMgdGhhdCBlYWNoIHRlc3QgYmUgZXZhbHVhdGVkIGF0IGEgbG93ZXIgbGV2ZWwgb2Ygc2lnbmlmaWNhbmNlICRcYWxwaGFfQiQgaW4gb3JkZXIgdG8gdG8gYWNoaWV2ZSBhIG5vbWluYWwgbGV2ZWwgb2Ygc2lnbmlmaWNhbmNlIG9mIDAuMDUuDQoNCklmIHdlIGFwcGx5IHRoaXMgY29ycmVjdGlvbiB0byB0aGUgYW5hbHlzaXMgYWJvdmUsIHdlIHNlZSB0aGF0IGluc3RlYWQgb2YgMC4wNSwgdGhlIHAtdmFsdWUgbmVlZGVkIGZvciBzaWduaWZpY2FuY2UgaXMgbXVjaCBsb3dlcjoNCmBgYHtyfQ0KYWxwaGFfQiA8LSAwLjA1L25yb3coZGYxX3NpbXVsYXRlZCkNCmFscGhhX0INCmBgYA0KDQpZb3UgY2FuIHZlcmlmeSBub3cgdGhhdCBubyBvYnNlcnZhdGlvbnMgaW4gYGRmMWAgc2hvdyB1cCBhcyBzaWduaWZpY2FudDoNCmBgYHtyfQ0Kc3VtKGRmMS5sZyRwLnZhbCA8PSBhbHBoYV9CKQ0KYGBgDQoNCklmIHdlIGV4YW1pbmUgdGhlIHZhcmlhYmxlIGluIGBkZjJgOg0KYGBge3J9DQpkZjIgPC0gbXV0YXRlKGRmMiwgDQogICAgICAgICAgICAgIFR5cGUgPSBmYWN0b3IoaWZlbHNlKEdzdGFyIDwgMCAmIHAudmFsIDw9IGFscGhhX0IsICJMb3cgQ29uY2VudHJhdGlvbiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShHc3RhciA+IDAgJiBwLnZhbCA8PSBhbHBoYV9CLCAiSGlnaCBDb25jZW50cmF0aW9uIiwgIk5vdCBTaWduaWNhbnQiKSkpKQ0KcGxvdF9seShkZjIsIHggPSB+eCwgeSA9IH55LCB6ID0gfnosIGNvbG9yID0gflR5cGUsIGNvbG9ycyA9IGMoInJlZCIsICJibHVlIiwgImdyYXkiKSwNCiAgICAgICAgbWFya2VyID0gbGlzdCgpKSAlPiUNCiAgYWRkX21hcmtlcnMoKQ0KYGBgDQoNCllvdSB3aWxsIHNlZSB0aGF0IGZld2VyIG9ic2VydmF0aW9ucyBhcmUgc2lnbmlmaWNhbnQsIGJ1dCBpdCBpcyBzdGlsbCBwb3NzaWJsZSB0byBkZXRlY3QgdHdvIHJlZ2lvbnMgb2YgaGlnaCBjb25jZW50cmF0aW9uLCBhbmQgdHdvIG9mIGxvdyBjb25jZW50cmF0aW9uLg0KDQpCb25mZXJyb25pIGNvcnJlY3Rpb24gaXMga25vd24gdG8gYmUgb3Zlcmx5IHN0cmljdCwgYW5kIHNoYXJwZXIgYXBwcm9hY2hlcyBleGlzdCB0byBjb3JyZWN0IGZvciBtdWx0aXBsZSB0ZXN0aW5nLiBCZXR3ZWVuIHRoZSBub21pbmFsIGxldmVsIChubyBjb3JyZWN0aW9uKSBhbmQgQm9uZmVycm9uaSBjb3JyZWN0aW9uLCBpdCBpcyBzdGlsbCBwb3NzaWJsZSB0byBhc3Nlc3MgdGhlIGdyYXZpdHkgb2YgdGhlIGlzc3VlIG9mIG11bHRpcGxlIGNvbXBhcmlzb25zLiBPYnNlcnZhdGlvbnMgdGhhdCBhcmUgZmxhZ2dlZCBhcyBzaWduaWZpY2FudCB3aXRoIHRoZSBCb25mZXJyb25pIGNvcnJlY3Rpb24sIHdpbGwgYWxzbyBiZSBzaWduaWZpY2FudCB1bmRlciBtb3JlIHJlZmluZWQgY29ycmVjdGlvbnMsIHNvIGl0IHByb3ZpZGVzIGEgbW9zdCBjb25zZXJ2YXRpdmUgZGVjaXNpb24gcnVsZS4NCg0KIyMgRGV0ZWN0aW9uIG9mIEhvdCBhbmQgQ29sZCBTcG90cw0KDQpBcyB0aGUgZXhhbXBsZXMgYWJvdmUgaWxsdXN0cmF0ZSwgbG9jYWwgc3RhdGlzdGljcyBjYW4gYmUgdmVyeSB1c2VmdWwgaW4gZGV0ZWN0aW5nIHdoYXQgbWlnaHQgYmUgdGVybWVkICJob3QiIGFuZCAiY29sZCIgc3BvdHMuIEEgX2hvdCBzcG90XyBpcyBhIGdyb3VwIG9mIG9ic2VydmF0aW9ucyB0aGF0IGFyZSBzaWduaWZpY2FudGx5IGhpZ2gsIHdoZXJlYXMgYSBfY29sZCBzcG90XyBpcyBhIGdyb3VwIG9mIG9ic2VydmF0aW9ucyB0aGF0IGFyZSBzaWduaWZpY2FudGx5IGxvdy4NCg0KVGhlcmUgYXJlIG1hbnkgZGlmZmVyZW50IGFwcGxpY2F0aW9ucyB3aGVyZSBob3QvY29sZCBzcG90IGRldGVjdGlvbiBpcyBpbXBvcnRhbnQuDQoNCkZvciBpbnN0YW5jZSwgaW4gbWFueSBzdHVkaWVzIG9mIHVyYmFuIGZvcm0sIGl0IGlzIGltcG9ydGFudCB0byBpZGVudGlmeSBjZW50ZXJzIGFuZCBzdWJjZW50ZXJzIC0gYnkgcG9wdWxhdGlvbiwgYnkgcHJvcGVydHkgdmFsdWVzLCBieSBpbmNpZGVuY2Ugb2YgdHJpcHMsIGFuZCBzbyBvbi4gSW4gc3BhdGlhbCBjcmltaW5vbG9neSwgZGV0ZWN0aW5nIGhvdCBzcG90cyBvZiBjcmltZSBjYW4gaGVscCB3aXRoIHByZXZlbnRpb24gYW5kIGxhdyBlbmZvcmNlbWVudCBlZmZvcnRzLiBJbiBlbnZpcm9ubWVudGFsIHN0dWRpZXMsIHJlbWVkaWF0aW9uIGVmZm9ydHMgY2FuIGJlIGdyZWF0bHkgYXNzaXN0ZWQgYnkgaWRlbnRpZmljYXRpb24gb2YgaG90IGFyZWFzLiBBbmQgc28gb24uDQoNCiMjIE90aGVyIFJlc291cmNlcw0KDQpDaGVjayBhIGNvb2wgYXBwIHRoYXQgaWxsdXN0cmF0ZXMgdGhlICRHX2leKiQgc3RhdGlzdGljIFtoZXJlXShodHRwOi8vcGVyc29uYWwudGN1LmVkdS9reWxld2Fsa2VyL3NwYXRpYWwtbmVpZ2hib3JzLWluLXIuaHRtbCk=